import { Ray } from './math/Ray.js';
import { Vector3 } from './math/Vector3.js';
import { Matrix4 } from './math/Matrix4.js';
import { Box3 } from "./math/Box3.js"

class Raycaster {
	constructor( origin, direction, near = 0, far = Infinity ) {
		this.ray = new Ray( origin, direction );
		this.near = near;
		this.far = far;
		this.camera = null;
	}

	set( origin, direction ) {
		// direction is assumed to be normalized (for accurate distance calculations)
		this.ray.set( origin, direction );

	}

	setFromCamera( coords, camera ) {
        let rayAt = camera.unproj([coords[0], coords[1], 0.5]);
        this.ray.origin.set( ...camera.trans.position );
        this.ray.direction.set( ...rayAt ).sub( this.ray.origin ).normalize();
		// this.camera = camera;

	}

    setFromPosition( pos1, pos2 ) {
        this.ray.origin.set( ...pos1 );
        this.ray.direction.set( ...pos2 ).sub( this.ray.origin ).normalize();
		// this.camera = camera;

	}

	intersectObject( object, recursive = true, intersects = [] ) {

		intersectObject( object, this, intersects, recursive );

		intersects.sort( ascSort );

		return intersects;

	}

	intersectObjects( objects, recursive = true, intersects = [] ) {
		objects.forEach(value => {
            intersectObject( value, this, intersects, recursive );
        })
		intersects.sort( ascSort );

		return intersects;

	}

	raycast( object, instance, instanceIndex, intersects ) {
        let mesh = object.getComponent(0);
        if(!mesh) {
            return []
        }
        if(!mesh.geometry) {
            return []
        }
        if(!mesh.geometry.position) {
            return []
        }
    
        let boundingBox = new Box3();
        boundingBox.setFromBuffer( mesh.geometry.position, 3 );
        boundingBox.applyMatrix4( instance.matrixWorld );
        let objectRay = new Ray();
        objectRay.copy( this.ray ).recast( this.near );
        if ( boundingBox.containsPoint( objectRay.origin ) === false ) {
			if ( objectRay.intersectsBox( boundingBox ) === false ) return;
		}
		//射线转成物体对应局部坐标系
        let inverseMatrix = new Matrix4();
        inverseMatrix.copy( instance.matrixWorld ).invert();
		objectRay.copy( this.ray ).applyMatrix4( inverseMatrix );
        this.computeIntersections( object, instanceIndex, intersects, objectRay );
        return intersects
	}
    raycastGeometry( geo, world) {
        let objectRay = new Ray();
        objectRay.copy( this.ray ).recast( this.near );
		//射线转成物体对应局部坐标系
        let inverseMatrix = new Matrix4();
        inverseMatrix.copy( instance.world ).invert();
		objectRay.copy( this.ray ).applyMatrix4( inverseMatrix );
        let intersects = this.computeGeoIntersections( geo, world, objectRay );
        return intersects
	}
    computeIntersections( object, instanceIndex, intersects, rayLocalSpace ) {
        let intersection;
        let mesh = object.getComponent(0);
        const positionData = mesh.geometry.position//获取顶点组件
        const indexData = mesh.geometry.indices;
        const cullMode = mesh.config.primitive.cullMode;
        if ( indexData.length ) {
            const start = 0;
            const end = indexData.length;
            for ( let i = start, il = end; i < il; i += 3 ) {
                const a = indexData[i];
                const b = indexData[i + 1];
                const c = indexData[i + 2];
                intersection = checkGeometryIntersection( object, instanceIndex, cullMode, this, rayLocalSpace, a, b, c );
                if ( intersection ) {
                    intersection.faceIndex = Math.floor( i / 3 ); // triangle number in indexed buffer semantics
                    intersects.push( intersection );
                }
            }
        } else if ( positionData.length ) {
            const start = 0;
            const end = positionData.length/3;
            for ( let i = start, il = end; i < il; i += 3 ) {
                const a = i;
                const b = i + 1;
                const c = i + 2;
                intersection = checkGeometryIntersection( object, instanceIndex, cullMode, this, rayLocalSpace, a, b, c );
                if ( intersection ) {
                    intersection.faceIndex = Math.floor( i / 3 ); // triangle number in non-indexed buffer semantics
                    intersects.push( intersection );
                }
            }
        }
    }
    
    computeGeoIntersections( geo, world, ray ) {
        let intersects = []
        let intersection;
        let _intersectionGeoPoint = [];
        const indexData = geo.indices;
        const position = geo.position;//获取顶点组件
        const start = 0;
        const end = indexData.length;
        for ( let i = start, il = end; i < il; i += 3 ) {
            const a = indexData[i];
            const b = indexData[i + 1];
            const c = indexData[i + 2];

            
            let va = [position[a*3], position[a*3+1], position[a*3+2]];
            let vb = [position[b*3], position[b*3+1], position[b*3+2]];
            let vc = [position[c*3], position[c*3+1], position[c*3+2]];
            

            let intersect;
            intersect = ray.intersectTriangle( vc, vb, va, false, _intersectionGeoPoint );
            if ( intersect === null ) continue;

            let _intersectionPointWorld = new Vector3();
            _intersectionPointWorld.copy( intersect );
	        _intersectionPointWorld.applyMatrix4( world );
            const distance = this.ray.origin.distanceTo( _intersectionPointWorld )
        
            if ( distance < this.near || distance > this.far ) continue;
        
            intersection =  {
                point: _intersectionPointWorld.clone(),
            };

            if ( intersection ) {
                intersection.faceIndex = Math.floor( i / 3 ); // triangle number in indexed buffer semantics
                intersects.push( intersection );
            }
        }
        return intersects
    }

}

function ascSort( a, b ) {

	return a.distance - b.distance;

}

function intersectObject( object, raycaster, intersects, recursive ) {
    if(object.type === "skybox") return [];
    if(object.trans.instanceCount === 1) {
        raycaster.raycast( object, object.trans, 0 ,intersects );
    } else {
        for(let i = 0 ; i  < object.trans.instanceCount; i++) {
            raycaster.raycast( object, object.trans._transArr[i], i ,intersects );
        }
    }
    
	if ( recursive === true ) {
		const children = object.children;
		for ( let i = 0, l = children.length; i < l; i ++ ) {
            let child = entityManager.get(children[i]);
			intersectObject( child, raycaster, intersects, true );
		}
	}
}
function checkIntersection( object, instanceIndex, cullMode, raycaster, ray, pA, pB, pC, point ) {
    let intersect;
    pA = new Vector3(...pA);
    pB = new Vector3(...pB);
    pC = new Vector3(...pC);
    if ( cullMode === "back" ) {
        intersect = ray.intersectTriangle( pA, pB, pC, true, new Vector3() );
    } else {
        intersect = ray.intersectTriangle( pC, pB, pA, ( cullMode === "front" ), new Vector3() );
    }
    if ( intersect === null ) return null;
    let _intersectionPointWorld = new Vector3();
    let trans = object.trans;
    _intersectionPointWorld.copy( intersect );
    if(trans.instanceCount === 1) {
        _intersectionPointWorld.applyMatrix4( trans.matrixWorld );
    } else {
        _intersectionPointWorld.applyMatrix4( trans._transArr[instanceIndex].matrixWorld );
    }
    
    const distance = raycaster.ray.origin.distanceTo( _intersectionPointWorld )

    if ( distance < raycaster.near || distance > raycaster.far ) return null;

    return {
        distance: distance,
        point: _intersectionPointWorld.clone(),
        pickId: object.id,
        object: object,
        instanceIndex,
        pointIndex: 0
    };

}
let _intersectionPoint = [];
function checkGeometryIntersection( object, instanceIndex, cullMode, raycaster, ray, a, b, c ) {
    let mesh = object.getComponent(0);
    const position = mesh.geometry.position;//获取顶点组件
    let va = [position[a*3], position[a*3+1], position[a*3+2]];
    let vb = [position[b*3], position[b*3+1], position[b*3+2]];
    let vc = [position[c*3], position[c*3+1], position[c*3+2]];
    const intersection = checkIntersection( object, instanceIndex, cullMode, raycaster, ray, va, vb, vc, _intersectionPoint );
    return intersection;

}


export { Raycaster };
